package com.dmai.capacity.platform.gateway.service.impl;

import cn.hutool.core.date.DateUtil;
import com.dmai.capacity.platform.common.lock.DistributedLock;
import com.dmai.capacity.platform.common.lock.LockAdapter;
import com.dmai.capacity.platform.common.model.CapabilityPackageDeductionRule;
import com.dmai.capacity.platform.common.model.OauthUserPackage;
import com.dmai.capacity.platform.common.model.OauthUserVolumeCharge;
import com.dmai.capacity.platform.common.utils.SpringUtil;
import com.dmai.capacity.platform.gateway.constants.RedisKeyConstant;
import com.dmai.capacity.platform.oauth.common.mapper.CapabilityPackageDeductionRuleDao;
import com.dmai.capacity.platform.oauth.common.mapper.OauthUserPackageDao;
import com.dmai.capacity.platform.oauth.common.mapper.OauthUserVolumeChargeDao;
import com.dmai.capacity.platform.gateway.service.IPermissionService;
import com.dmai.capacity.platform.oauth.common.constants.ResourcePackageTypeEnum;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 校验能不能调用接口
 */
@Slf4j
@Service
public class PermissionServiceImpl implements IPermissionService {

    @Resource
    OauthUserPackageDao packageDao;

    @Resource
    OauthUserVolumeChargeDao volumeChargeDao;

    @Resource
    CapabilityPackageDeductionRuleDao deductionRuleDao;

    @Autowired
    private DistributedLock locker;


    @Value("${dmai.redis.cache.cache_user_package.minute:5}")
    private Integer CACHE_USER_PACKAGE_MINUTE;

    @Value("${dmai.redis.cache.cache_user_volumeCharge.minute:60}")
    private Integer CACHE_USER_VOLUMECHARGE_MINUTE;


    /**
     * 1. 判断token是否有效 2. 判断是否有资源包可以用，返回资源包id，3 判断是否开通按量后付费
     *
     * @param userid
     * @param capabilityId
     * @return
     */
    @Override
    @SneakyThrows
    public Integer checkPermission(Integer userid, Integer capabilityId) {
        try (LockAdapter lockAdapter = locker.lock(RedisKeyConstant.LOCK_USER_CAPABILITY_PERMISSION_PREFIX + ":" + userid + ":" + capabilityId);) {
            // 1. 查看是否还有有效的资源包
            OauthUserPackage effectivePackage = getPriorityEffectivePackage(userid, capabilityId);
            if (effectivePackage != null) {
                return effectivePackage.getId();
            }
            // 2. 查看是否开通按量后付费
            return hasOpenVolumeCharge(userid, capabilityId) ? 0 : null;
        }
    }


    /**
     * 获取一个优先级最高的有效资源包
     *
     * @param userid
     * @param capabilityId
     * @return
     */
    private OauthUserPackage getPriorityEffectivePackage(Integer userid, Integer capabilityId) {
        Redisson redisson = SpringUtil.getBean(Redisson.class);
        List<OauthUserPackage> packages;
        RBucket<List<OauthUserPackage>> cache = redisson.getBucket(RedisKeyConstant.CACHE_USER_PACKAGE_PREFIX + ":" + userid + ":" + capabilityId);
        if (cache.get() == null) {
            packages = packageDao.findEffectiveByCapability(userid, capabilityId);
            cache.set(packages, CACHE_USER_PACKAGE_MINUTE, TimeUnit.MINUTES);
        } else {
            packages = cache.get();
        }
        packages = packages.stream().filter(p -> p.getEffective().equals(1) && DateUtil.date().compareTo(p.getApplyTime()) >= 0).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(packages)) {
            return null;
        }
        // 优先使用专项最快过期的资源包
        packages.sort(Comparator.comparing(OauthUserPackage::getType, Comparator.nullsLast(Integer::compareTo).reversed()).thenComparing(OauthUserPackage::getExpiredTime, Comparator.nullsLast(Date::compareTo)).reversed());

        //预先更新资源包可用点数
        updatePackageAvailableCachaAdvance(userid, capabilityId, packages.get(0).getId());

        return packages.get(0);
    }

    /**
     * 提前预先更新资源包可用点数，防止接口回调期间客户端大量请求突破阈值
     */
    private void updatePackageAvailableCachaAdvance(int userid, int capabilityId, int packageId) {
        Redisson redisson = SpringUtil.getBean(Redisson.class);
        RBucket<List<OauthUserPackage>> cache = redisson.getBucket(RedisKeyConstant.CACHE_USER_PACKAGE_PREFIX + ":" + userid + ":" + capabilityId);
        if (cache.get() != null) {
            List<OauthUserPackage> cacheData = cache.get();
            Optional<OauthUserPackage> first = cacheData.stream().filter(p -> p.getId().equals(packageId)).findFirst();
            if (first.isPresent()) {
                int _deduction = 1;
                if (first.get().getType().equals(ResourcePackageTypeEnum.SHARE.getCode())) {
                    // 共享资源包
                    CapabilityPackageDeductionRule rule = deductionRuleDao.find(capabilityId);
                    _deduction = rule.getCount();
                }

                if (first.get().getQuotaAvailable() >= _deduction) {
                    first.get().setQuotaAvailable(first.get().getQuotaAvailable() - _deduction);
                    if (first.get().getQuotaAvailable() <= 0) {
                        first.get().setEffective(0);
                    }
                } else {
                    // 不够扣除扣一次，资源包失效
                    first.get().setQuotaAvailable(0);
                    first.get().setEffective(0);
                }
                if (first.get().getExpiredTime().compareTo(new Date()) <= 0) {
                    first.get().setEffective(0);
                }

                cache.set(cacheData, CACHE_USER_PACKAGE_MINUTE, TimeUnit.MINUTES);
            }
        }
    }


    /**
     * 判断是否开通按量后付费
     */
    private boolean hasOpenVolumeCharge(int userid, int capabilityId) {
        Redisson redisson = SpringUtil.getBean(Redisson.class);
        RBucket<Boolean> cache = redisson.getBucket(RedisKeyConstant.CACHE_USER_VOLUMECHARGE_PREFIX + ":" + userid + ":" + capabilityId);
        if (cache.get() == null) {
            OauthUserVolumeCharge charge = volumeChargeDao.findByCapability(userid, capabilityId);
            boolean openVolumCharge = false;
            if (charge != null && charge.getState() == 1) {
                openVolumCharge = true;
            }
            cache.set(openVolumCharge, CACHE_USER_VOLUMECHARGE_MINUTE, TimeUnit.MINUTES);
        }
        return cache.get();
    }
}
